package cn.sc.summer.log.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.sc.summer.log.annotation.Log;
import cn.sc.summer.log.builder.LogInfoBuilder;
import cn.sc.summer.log.event.LogEvent;
import cn.sc.summer.constant.log.SysLog;
import cn.sc.summer.log.util.LogUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import cn.sc.summer.constant.project.DateConstant;
import cn.sc.summer.constant.project.ProjectConstant;
import cn.sc.summer.constant.token.TokenConstant;
import cn.sc.summer.core.util.SpringSecurityUserUtil;
import cn.sc.summer.exception.enums.CommonExceptionEnum;
import cn.sc.summer.exception.hodler.SpringContextHolder;
import cn.sc.summer.exception.util.ip.IPUtil;
import cn.sc.summer.log.builder.LogBuilderDirector;
import cn.sc.summer.token.po.UserDetailX;
import io.swagger.annotations.ApiOperation;
import javassist.ClassPool;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

@Slf4j
@Component
@Aspect
@EnableAspectJAutoProxy
public class LogAop {

    public static final String TRACE_ID = "traceId";

    @Resource
    private ApplicationContext applicationContext;

    @Around("@annotation(org.springframework.web.bind.annotation.PostMapping) " +
            "|| @annotation(org.springframework.web.bind.annotation.GetMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.PutMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.DeleteMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable {
        return this.saveApiLog(joinPoint);
    }

    /**
     * 入库接口访问日志信息
     *
     * @param joinPoint 切点
     * @return 返回结果
     * @throws Throwable 异常信息
     */
    public Object saveApiLog(ProceedingJoinPoint joinPoint) throws Throwable {

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        CommonExceptionEnum.NORMAL_EXCEPTION.isNull(requestAttributes, "The request was exceptional, please try again！");
        HttpServletRequest request = requestAttributes.getRequest();
        String requestURI = request.getRequestURI();

        Map<String, String> parameterMap = LogUtil.getParams(request, joinPoint);
        StringBuilder paramString = new StringBuilder();

        Class<?> clazz = joinPoint.getTarget().getClass();
        String strClassName = clazz.getName();
        String[] split = strClassName.split("\\.");
        String strMethodName = joinPoint.getSignature().getName();

        ClassPool classPool = ClassPool.getDefault();
        Integer lineNumber;
        try {
            lineNumber = classPool.get(strClassName).getDeclaredMethod(strMethodName).getMethodInfo().getLineNumber(0);
        } catch (Exception e) {
            lineNumber = 1;
        }

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        String remark = "";
        if (ObjectUtil.isNotNull(apiOperation) && method.isAnnotationPresent(ApiOperation.class)) {
            remark = apiOperation.value();
        }
        Log logInfo = method.getAnnotation(Log.class);
        if (ObjectUtil.isNotNull(logInfo) && method.isAnnotationPresent(Log.class)) {
            remark = logInfo.value();
        }

        StringBuilder stringBuilder = new StringBuilder("\n\tSpringBoot controller report --------- ").append(new SimpleDateFormat(DateConstant.YYYY_MM_DD_HH_MM_SS_1).format(new Date())).append(" -----------------------------");
        stringBuilder.append("\n\tType \t: ").append(request.getHeader(TokenConstant.REQUEST_TYPE))
                .append(" / ").append(request.getMethod())
                .append(" / ").append(MDC.get(TRACE_ID));
        if (CharSequenceUtil.isNotBlank(remark)) {
            stringBuilder.append(" / ").append(remark);
        }
        stringBuilder.append("\n\tURL  \t: ").append(IPUtil.getIpAddr(request)).append(" -> ").append("http://").append(request.getHeader("host")).append(requestURI);
        stringBuilder.append("\n\tClass  \t: ").append(strClassName).append(".").append(strMethodName).append("(").append(split[split.length - 1]).append(".java:").append(lineNumber).append(")");
        stringBuilder.append("\n\tParams \t: ");
        if (CollUtil.isNotEmpty(parameterMap)) {
            stringBuilder.append(JSONObject.toJSONString(parameterMap));
            paramString.append(JSONObject.toJSONString(parameterMap));
        } else {
            stringBuilder.append("This method has no parameters!");
        }
        stringBuilder.append("\n\t----------------------------------------------------------------------------------------");
        log.info(stringBuilder.toString());

        //使用构造者模式获取请求头等信息
        SysLog sysLogInfo = new LogBuilderDirector(new LogInfoBuilder()).build();
        sysLogInfo.setApiClass(strClassName);
        sysLogInfo.setApiMethod(strMethodName);
        sysLogInfo.setApiServiceName(ProjectConstant.SERVER_NAME);
        sysLogInfo.setLogTitle(remark);
        sysLogInfo.setReqParams(StrUtil.isNotBlank(paramString.toString()) ? paramString.toString() : "The method has no parameters！");

        try {
            UserDetailX loginUser = SpringSecurityUserUtil.getLoginUser();
            sysLogInfo.setCreateUser(loginUser.getUserId());
            sysLogInfo.setUpdateUser(loginUser.getUserId());
            sysLogInfo.setDataTenantId(loginUser.getTenantId());
        } catch (Exception ignored) {
        }

        // 定义返回数据
        Object result;
        long startTime = System.currentTimeMillis();
        try {
            result = joinPoint.proceed();
            HttpServletResponse response = SpringContextHolder.getHttpServletResponse();
            sysLogInfo.setRespStatus(response.getStatus());
            String respData = result == null ? "null" : JSON.toJSONString(result);
            sysLogInfo.setRespData(respData);
        } catch (Throwable e) {
            sysLogInfo.setException(e.getMessage());
            sysLogInfo.setRespStatus(500);
            throw e;
        } finally {
            sysLogInfo.setTime(System.currentTimeMillis() - startTime);
            try {
                //将日志封装的数据发布到上下文中
                applicationContext.publishEvent(new LogEvent(sysLogInfo));
            } catch (Exception ignored) {
            }
        }

        return result;
    }

}
